package conf

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
	orm_mysql "gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func DefaultConfig() *Config {
	return &Config{
		App:     newDefaultApp(),
		MySQL:   newDefaulMySQL(),
		Http:    newDefaultHttp(),
		Grpc:    NewDefaultGrpc(),
		MongoDB: newDefaultMongoDB(),
		Jaeger:  newJaeger(),
	}
}

// 程序的配置对象 config --> object
type Config struct {
	App *App `json:"app" toml:"app"`
	// [mysql]
	MySQL *MySQL `json:"mysql" toml:"mysql"`
	//
	MongoDB *mongodb `json:"mongo" toml:"mongo"`
	// [http]
	Http *Http `json:"http" toml:"http"`
	// [grpc]
	Grpc *Grpc `json:"grpc" toml:"grpc"`
	// 程序接入Jaeger配置
	Jaeger *jaeger `toml:"jaeger"`
}

func newDefaultApp() *App {
	return &App{
		Name: "mcenter",
	}
}

type App struct {
	Name string `json:"name" toml:"name"`
}

// 格式化成一个json
func (c *Config) String() string {
	d, _ := json.MarshalIndent(c, "", "  ")
	return string(d)
}

type MySQL struct {
	Host     string `json:"host" toml:"host" env:"MYSQL_HOST"`
	Port     int    `json:"port" toml:"port" env:"MYSQL_PORT"`
	DB       string `json:"db" toml:"db" env:"MYSQL_DB"`
	Username string `json:"username" toml:"username" env:"MYSQL_USERNAME"`
	Password string `json:"password" toml:"password" env:"MYSQL_PASSWORD"`

	// 高级参数
	MaxOpenConn int `toml:"max_open_conn" env:"MYSQL_MAX_OPEN_CONN"`
	MaxIdleConn int `toml:"max_idle_conn" env:"MYSQL_MAX_IDLE_CONN"`
	MaxLifeTime int `toml:"max_life_time" env:"MYSQL_MAX_LIFE_TIME"`
	MaxIdleTime int `toml:"max_idle_time" env:"MYSQL_MAX_IDLE_TIME"`

	// 面临并发安全
	lock sync.Mutex
	db   *gorm.DB
}

func (m *MySQL) Close() error {
	if m.db == nil {
		return nil
	}

	// 没有提供Close方法
	// 没法实现数据库的优雅关闭
	return nil
}

func (m *MySQL) GetConnPool() (*sql.DB, error) {
	var err error
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&multiStatements=true",
		m.Username, m.Password, m.Host, m.Port, m.DB)
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, fmt.Errorf("connect to mysql<%s> error, %s", dsn, err.Error())
	}

	// 对连接池进行设置
	db.SetMaxOpenConns(m.MaxOpenConn)
	db.SetMaxIdleConns(m.MaxIdleConn)
	if m.MaxLifeTime != 0 {
		db.SetConnMaxLifetime(time.Second * time.Duration(m.MaxLifeTime))
	}
	if m.MaxIdleConn != 0 {
		db.SetConnMaxIdleTime(time.Second * time.Duration(m.MaxIdleTime))
	}

	// 加了一个Ping
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		return nil, fmt.Errorf("ping mysql<%s> error, %s", dsn, err.Error())
	}
	return db, nil
}

func (m *MySQL) ORM() *gorm.DB {
	m.lock.Lock()
	defer m.lock.Unlock()

	if m.db == nil {
		// 初始化DB
		// 1.1 获取sql.DB
		p, err := m.GetConnPool()
		if err != nil {
			panic(err)
		}

		// 1.2 使用pool 初始化orm db对象
		m.db, err = gorm.Open(orm_mysql.New(orm_mysql.Config{
			Conn: p,
		}), &gorm.Config{
			// 执行任何 SQL 时都创建并缓存预编译语句，可以提高后续的调用速度
			PrepareStmt: true,
			// 对于写操作（创建、更新、删除），为了确保数据的完整性，GORM 会将它们封装在事务内运行。
			// 但这会降低性能，如果没有这方面的要求，您可以在初始化时禁用它，这将获得大约 30%+ 性能提升
			SkipDefaultTransaction: true,
			// 要有效地插入大量记录，请将一个 slice 传递给 Create 方法
			// CreateBatchSize: 200,
		})
		if err != nil {
			panic(err)
		}
	}
	return m.db
}

func newDefaulMySQL() *MySQL {
	return &MySQL{
		Host:     "127.0.0.1",
		Port:     3306,
		DB:       "mcenter",
		Username: "root",
		Password: "123456",
	}
}

func newDefaultHttp() *Http {
	return &Http{
		Host: "127.0.0.1",
		Port: 8010,
	}
}

type Http struct {
	Host string `json:"host" toml:"host" env:"HTTP_HOST"`
	Port int    `json:"port" toml:"port" env:"HTTP_PORT"`
}

func (h *Http) Address() string {
	return fmt.Sprintf("%s:%d", h.Host, h.Port)
}

func NewDefaultGrpc() *Grpc {
	return &Grpc{
		Host: "127.0.0.1",
		Port: 18010,
	}
}

type Grpc struct {
	Host string `json:"host" toml:"host" env:"MCENTER_GRPC_HOST"`
	Port int    `json:"port" toml:"port" env:"MCENTER_GRPC_PORT"`
}

func (h *Grpc) Address() string {
	return fmt.Sprintf("%s:%d", h.Host, h.Port)
}

func newDefaultMongoDB() *mongodb {
	m := &mongodb{
		UserName:  "mcenter",
		Password:  "123456",
		Database:  "mcenter_mini",
		AuthDB:    "",
		Endpoints: []string{"127.0.0.1:27017"},
	}
	return m
}

type mongodb struct {
	Endpoints []string `toml:"endpoints" env:"MONGO_ENDPOINTS" envSeparator:","`
	UserName  string   `toml:"username" env:"MONGO_USERNAME"`
	Password  string   `toml:"password" env:"MONGO_PASSWORD"`
	Database  string   `toml:"database" env:"MONGO_DATABASE"`
	AuthDB    string   `toml:"auth_db" env:"MONGO_AUTH_DB"`

	client *mongo.Client
	lock   sync.Mutex
}

func (m *mongodb) GetAuthDB() string {
	if m.AuthDB != "" {
		return m.AuthDB
	}

	return m.Database
}

func (m *mongodb) GetDB() (*mongo.Database, error) {
	conn, err := m.Client()
	if err != nil {
		return nil, err
	}
	return conn.Database(m.Database), nil
}

// 关闭数据库连接
func (m *mongodb) Close(ctx context.Context) error {
	if m.client == nil {
		return nil
	}

	return m.client.Disconnect(ctx)
}

// Client 获取一个全局的mongodb客户端连接
func (m *mongodb) Client() (*mongo.Client, error) {
	// 加载全局数据量单例
	m.lock.Lock()
	defer m.lock.Unlock()

	if m.client == nil {
		conn, err := m.getClient()
		if err != nil {
			return nil, err
		}
		m.client = conn
	}

	return m.client, nil
}

func (m *mongodb) getClient() (*mongo.Client, error) {
	opts := options.Client()

	if m.UserName != "" && m.Password != "" {
		cred := options.Credential{
			AuthSource: m.GetAuthDB(),
		}

		cred.Username = m.UserName
		cred.Password = m.Password
		cred.PasswordSet = true
		opts.SetAuth(cred)
	}
	opts.SetHosts(m.Endpoints)
	opts.SetConnectTimeout(5 * time.Second)
	// 添加MongoDB的trace
	opts.Monitor = otelmongo.NewMonitor(
		otelmongo.WithCommandAttributeDisabled(true),
	)

	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*5))
	defer cancel()

	// Connect to MongoDB
	client, err := mongo.Connect(ctx, opts)
	if err != nil {
		return nil, fmt.Errorf("new mongodb client error, %s", err)
	}

	if err = client.Ping(ctx, nil); err != nil {
		return nil, fmt.Errorf("ping mongodb server(%s) error, %s", m.Endpoints, err)
	}

	return client, nil
}

func newJaeger() *jaeger {
	// 使用官方默认值
	return &jaeger{
		Endpoint: "http://localhost:14268/api/traces",
	}
}

type jaeger struct {
	Endpoint string `toml:"endpoint" json:"endpoint" yaml:"endpoint" env:"JAEGER_ENDPOINT"`
}
